iOS 多线程(1)

您所在的位置:网站首页 img1 3lian iOS 多线程(1)

iOS 多线程(1)

2022-12-29 04:36| 来源: 网络整理| 查看: 265

1. 线程和进程

在了解多线程之前,我们现在熟悉两个概念,线程和进程.

一. 线程

线程:进程的基本执行单元,一个进程的所有任务都是在线程中执行. 进程要想执行任务,必须得有线程,进程至少有一条线程. 程序启动会默认开始一条线程,这条线程被称为主线程.

二.进程

进程:在系统中正在运行的一个应用程序. 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存中.

三. 进程与线程的区别: 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间. 资源拥有:同一进程内的线程共享本进程的资源如内存,I/O,CPU等,但是进程之前的资源是独立的. 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉.所以多进程要比多线程健壮. 进程切换时,消耗的资源大,效率低.所以设计到频繁的切换时,使用线程要高于进程.同样如果要求同时进行又要共享某些变量的并发操作,只能用线程不能用进程. 执行过程:每个独立的进程有一个程序运行的入口,顺序执行序列.但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制. 线程是处理器调度的基本单元,但是进程不是. 2. iOS中的多线程

我们知道了进程和线程之间的关系和区别,我们现在再来说一下iOS中有哪些多线程.多线程有哪些?他们之前的区别是什么?我们在开发的时候选择哪种方式会更好一些?

技术方案 简介 语言 线程生命周期 使用频率 pthread 1.一套通用的多线程API. 2.适用于Unix\Linux\windows等系统.3.可快平台\可移植.4.使用难度大. C 程序员管理 几乎不使用(在加锁的时候使用较多) NSThread 1.使用更加面向对象. 2.简单易懂,可直接操作线程对象. OC 程序员管理 偶尔使用 GCD 1.旨在替代NSThread等线程技术. 2.充分利用设备的多核. C 系统管理 经常使用 NSOperation 1.基于GCD(底层是GCD). 2.比GCD多了一些更简单实用的功能.3.使用更加面向对象. OC 系统管理 经常使用

NSThread的底层是pthread,NSPperation的底层是GCD.GCD、NSPperation、NSThread创建线程都是依赖于pethread.

一. pthread的使用 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //创建线程 pthread_t thread; /* 第一个参数pthread_t *restrict:线程对象 第二个参数const pthread_attr_t *restrict:线程属性 第三个参数void *(*)(void *) :指向函数的指针 第四个参数void *restrict:函数的参数 */ pthread_create(&thread, NULL,eat ,NULL); } //void *(*)(void *) void *eat(void *param) { NSLog(@"调用了该方法"); } 二. NSThread的使用 A. NSThread创建 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 方法一:创建线程,需要自己开启线程 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(eat) object:nil]; // 开启线程 [thread start]; // 方法二:创建线程后自动启动线程 [NSThread detachNewThreadSelector:@selector(eat) toTarget:self withObject:nil]; // 方法三:隐式创建并启动线程 [self performSelectorInBackground:@selector(eat) withObject:nil]; }

后面两种方法都不用我们开启线程,相对方便快捷,但是没有办法拿到子线程对象,没有办法对子线程进行更详细的设置.

B. NSThread的方法 // 获取当前线程 + (NSThread *)currentThread; // 创建启动线程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 判断是否是多线程 + (BOOL)isMultiThreaded; // 线程休眠 NSDate 休眠到什么时候 + (void)sleepUntilDate:(NSDate *)date; // 线程休眠时间 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 结束/退出当前线程 + (void)exit; // 获取当前线程优先级 + (double)threadPriority; // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 // 1.0优先级最高 // 设置优先级 + (BOOL)setThreadPriority:(double)p; // 获取指定线程的优先级 - (double)threadPriority NS_AVAILABLE(10_6, 4_0); - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0); // 设置线程的名字 - (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0); // 判断指定的线程是否是 主线程 - (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // 判断当前线程是否是主线程 + (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main // 获取主线程 + (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0); - (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer // 创建线程 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0); // 指定线程是否在执行 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); // 线程是否完成 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); // 线程是否被取消 (是否给当前线程发过取消信号) - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); // 发送线程取消信号的 最终线程是否结束 由 线程本身决定 - (void)cancel NS_AVAILABLE(10_5, 2_0); // 启动线程 - (void)start NS_AVAILABLE(10_5, 2_0); // 线程主函数 在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法 - (void)main NS_AVAILABLE(10_5, 2_0); // thread body method C.NSThread线程的状态 NSThread线程的状态.png //启动线程 - (void)start; // 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态 //阻塞(暂停)线程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 进入阻塞状态 //强制停止线程 + (void)exit; // 进入死亡状态 D. NSThread线程之间的通信 线程间通信 在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信. 线程间通信的应用 一个线程传递数据给另一个线程,在一个线程中执行完特定任务后,转到另一个线程继续执行任务.

线程间通信常用的方法

// 返回主线程 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // 返回指定线程 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

demo:

#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [NSThread detachNewThreadSelector:@selector(donwLoadImage) toTarget:self withObject:nil]; } -(void)donwLoadImage { // 获取图片url地址 http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg NSURL *url = [NSURL URLWithString:@"http://www.itunes123.com/uploadfile/2016/0421/20160421014340186.jpg"]; // 下载图片二进制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 将图片二进制文件转化为image; UIImage *image = [UIImage imageWithData:data]; // 参数 waitUntilDone 是否等@selector(showImage:) 执行完毕以后再执行下面的操作 YES :等 NO:不等 // 返回主线程显示图片 // [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; // self.imageView 也可以直接调用这个方法 直接选择 setImage方法,传入参数image即可 // [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; // 返回特定的线程,[NSThread mainThread] 获得主线程 [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES]; } -(void)showImage:(UIImage *)image { self.imageView.image = image; } @end 三. GCD的使用 A. GCD的任务和队列 a. GCD的任务

GCD任务:执行什么操作,任务有两种执行方式: 同步函数 (dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);)和 异步函数(dispatch_async(dispatch_queue_t queue, dispatch_block_t block);). 同步:只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行. 异步:可以在新的线程中执行任务,具备开启新线程的能力,但不一定会开新线程,当前线程会直接往下执行,不会阻塞当前线程.

b. GCD的队列

GCD的队列:用来存放任务,分为串行队列 和 并行队列. 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务). 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步(dispatch_async)函数下才有效.

GCD的使用就2个步骤:

定制任务:确定想做的事情. 将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行. 任务的取出遵循队列的FIFO原则:先进先出,后进后出. B. GCD的创建

a. 队列的创建:

// 第一个参数const char *label : C语言字符串,用来标识 // 第二个参数dispatch_queue_attr_t attr : 队列的类型 // 并发队列:DISPATCH_QUEUE_CONCURRENT // 串行队列:DISPATCH_QUEUE_SERIAL 或者 NULL dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

创建并发队列:

dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);

创建串行队列:

dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);

GCD默认已经提供了全局并发队列,供整个应用使用,可以无需手动创建:

/** 第一个参数:优先级 也可直接填后面的数字 #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台 第二个参数: 预留参数 0 */ dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

获得主队列:

dispatch_queue_t queue = dispatch_get_main_queue();

b. 任务的执行: 开启同步函数,同步函数:在当前线程中执行任务,不具备开启新线程的能力,要求立刻马上开始执行.

/* 第一个参数:队列 第二个参数:block,在里面封装任务 */ dispatch_sync(queue, ^{ });

开启异步函数,异步函数 :在新的线程中执行任务,具备开启新线程的能力(如果在主队的话,是不能开启新线程的),等主线程执行完毕之后,回过头开线程执行任务.

dispatch_async(queue, ^{ });

c. 任务和队列的组合 任务:同步函数 异步函数. 队列:串行 并行. 异步函数+并发队列:会开启新的线程,并发执行.

dispatch_queue_t queue = dispatch_get_global_queue(0,0); dispatch_async(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务1-%@",[NSThread crruentThraed]); } }); dispatch_async(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务2-%@",[NSThread crruentThraed]); } }); //因为是并发队列的异步执行,所以开启新的线程去完成queue中的任务. //所以执行任务1和执行任务2是交替进行的.

异步函数+串行队列:会开启一条线程,任务串行执行.

dispatch_queue_t queue = dispatch_queue_create("myqueue",DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务1-%@",[NSThread crruentThraed]); } }); dispatch_async(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务2-%@",[NSThread crruentThraed]); } }); //因为是串行队列,所以会在子线程执行,会在任务1执行结束采取执行任务2

同步函数+并发队列:不会开线程,任务串行执行

dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });

同步函数+串行队列:不会开线程,任务串行执行

dispatch_queue_t queue = dispatch_queue_create("myqueue",DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务1-%@",[NSThread crruentThraed]); } }); dispatch_sync(queue, ^{ for(int i; i < 10;i++ ){ NSLog(@"执行任务2-%@",[NSThread crruentThraed]); } }); //因为是串行队列,所以会在主线程执行,会在任务1执行结束采取执行任务2

异步函数+主队列:不会开线程,任务串行执行 使用主队列(跟主线程相关联的队列). 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行.

//1.获得主队列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.异步函数 dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });

同步函数+主队列:死锁

//1.获得主队列 dispatch_queue_t queue = dispatch_get_main_queue(); //2.同步函数 dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); });

因为这个方法在主线程中,给主线程中添加任务,而同步函数要求立刻马上执行,因此就会相互等待而发生死锁。将这个方法放入子线程中,则不会发生死锁,任务串行执行。

并发队列 手动创建串行队列 主队列 同步(sync) 1.没有开启新线程. 2.串行执行任务. 1.没有开启新线程. 2.串行执行任务. 1.没有开启新线程. 2.串行执行任务. 异步(async) 1.有开启新线程. 2.并发执行任务. 1.有开启新线程. 2.串行执行任务. 1.没有开启新线程. 2.串行执行任务.

d.同步函数和异步函数的执行顺序 同步函数:立刻马上执行,会阻塞当前线程

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; } //同步函数+并发队列:不会开线程,任务串行执行 -(void)syncConcurrent { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); NSLog(@"--syncConcurrent--start-"); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--syncConcurrent--end-"); }

我们看一下Log

同步函数会阻塞线程,立即执行.png

异步函数:当前线程会直接往下执行,不会阻塞当前线程

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self syncConcurrent]; } //异步函数+并发队列:会开启新的线程,并发执行 -(void)asyncCONCURRENT { NSLog(@"--asyncCONCURRENT--start-"); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download2---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download3---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"---download4---%@",[NSThread currentThread]); }); NSLog(@"--asyncCONCURRENT--end-"); }

我们来看一下Log

异步函数不会阻塞当前线程.png

注意:GCD中开多少条线程是由系统根据CUP繁忙程度决定的,如果任务很多,GCD会开启适当的子线程,并不会让所有任务同时执行。

C. GCD线程间的通信

demo

import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ // 获得图片URL NSURL *url = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/2301429-d5cc0a007447e469.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"]; // 将图片URL下载为二进制文件 NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制文件转化为image UIImage *image = [UIImage imageWithData:data]; NSLog(@"%@",[NSThread currentThread]); // 返回主线程 这里用同步函数不会发生死锁,因为这个方法在子线程中被调用。 // 也可以使用异步函数 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; NSLog(@"%@",[NSThread currentThread]); }); }); } @end

GCD线程间的通信非常简单,使用同步或异步函数,传入主队列即可。

D. GCD其他常用函数

a. 栅栏函数(控制任务的执行顺序)

dispatch_barrier_async(queue, ^{ NSLog(@"--dispatch_barrier_async-"); });

我们来看一下栅栏函数的作用

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self barrier]; } -(void)barrier { //1.创建队列(并发队列) dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (NSInteger i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3